
// 仿照vue指令  v-model
// 使用方法  绑定DOM
// 也可以主动使用
// 模式分为  写入 与 获取
// 解决对象深入写入与获取
/*
   ## 普通用法  vue示例
   // 绑定event对象
   <view @click="$model(null,$event)" data-model="info.name" data-value="my name">{{ info.name }}</view>
   {
     data(){
       return{
         list:[1,2]
         ,info:{
            name:"name"
            ,age:18
         }
       }
     },
     methods:{
       //主动调用
        setList(){
          this.$model({
            'list[0]':3
          })
        }
     }
   }

   ###  2021/3/29 新增特性  -- 写入的value可以为一个 函数  
        函数的第一个参数  为  即将正确写入的 key 之前的值 
        函数的this指向  为  调用model的对象

        const obj = { info:{ name:12 } }
        model({
          ["info.name"](ov){
            console.log("info.name" , ov)
            return "nameVal"
          }
        })
      

   ###   2022/1/15 新增特性  -- 获取指定载入路径的值 
        const obj = { info:{ name:12 } , list:[1,2] }
        const modelVal =  model("info.name")
        console.log( modelVal )
        const modelVals =  model(["info.name" , "list[1]"])
        console.log( modelVals )
  */
/**
 * @description: 仿照微信小程序model函数(具备其所有特性) ， 并在其之上做了一些扩展。
 * @param {Object} keyObj //自定义密钥配置对象
 * @param {Event}  event  // DOM Event 对象
 * @param {Object} modelConfig  [deep // 针对vue数组对象是否进行深度相应
 *                     , module // 模块路径会拼接在每个载入路径前面
 *                     , callback // 回调函数，第一个参数为所有载入的集合对象 第二个参数为所有载入对象path:value的集合对象]
 * @return {void}
 */
const comUtils = require("./common");

function model( keyObj,event, modelConfig) {
  let AsskeyObj;
  // 载入模式  true === 载入  false === 获取
  let modelType = true;
  let keyObjType;
  // 进行参数合并  两者选其一
  if (keyObj) {
    AsskeyObj = {};
    keyObjType = comUtils.toClass(keyObj);
    // 如果是对象模式   默认载入模式 载入
    if (keyObjType === "Object") {
      AsskeyObj = keyObj;
      // 字符串类型 或者 数组类型  载入模式  获取
    } else if (keyObjType === "String") {
      AsskeyObj[keyObj] = function (ov) {
        return ov;
      };
      modelType = false;
    } else if (keyObjType === "Array") {
      keyObj.forEach((keyPath) => {
        AsskeyObj[keyPath] = function (ov) {
          return ov;
        };
      });
      modelType = false;
    }
    // 如果是获取模式  改变深度载入为false  减少深度载入消耗
    if (!modelType) {
      modelConfig = {
        deep: false,
      };
    }
    if (Object.keys(AsskeyObj).length === 0) {
      throw Error("model object length is null");
    }
  }else {
    if (event) {
      //DOMStringMap  https://developer.mozilla.org/zh-CN/docs/Web/API/DOMStringMap
      //兼容性： ie >= 11 其他浏览器在低版本都已兼容
      const eventData = event.currentTarget.dataset;
      const edModel = eventData.model;
      if (edModel) {
        AsskeyObj = {};
        // 如果指定载入模式
        if (!comUtils.hasOwn(eventData, "value", false)) {
          throw Error("Please enter the model value");
        }
        AsskeyObj[edModel] = eventData.value;
      } else {
        throw new Error("the event model is must be required");
      }
    }
  }

  if (AsskeyObj) {
    // 保存需要写入的所有对象
    // 减少 model 合并额外没有变动属性
    const modelAss = {};
    // model-path 收集载入路径 与 value
    const modelAss_path = {};
    const AsskeyObj_keys = Object.keys(AsskeyObj);
    // 获取载入配置参数
    modelConfig = Object.assign(
      {
        deep: true,
      },
      modelConfig
    );
    const modelHasCallback = comUtils.isType(modelConfig.callback, "Function");
    if (AsskeyObj_keys.length) {
      AsskeyObj_keys.forEach((keyNode) => {
        // 获取value
        const value = AsskeyObj[keyNode];
        // 为自定义新值提供可配置函数 arguments[0]为旧值
        const valueIsFun = typeof value === "function";

        let modelPath = keyNode;
        // 判断模块是否存在 为写入路径拼接上模块路径
        if (modelConfig.module && typeof modelConfig.module === "string") {
          modelPath = modelConfig.module + "." + modelPath;
        }
        // 如果存在分割符  , 或者 | 转换为数组
        // 消除空格
        const modelArr = comUtils.trimAll(modelPath, true).split(/[,|]/);
        // 对分割后的model进行循环写入
        modelArr.forEach((modelNode) => {
          // 转换modelNode上的数组元素为统一分隔符
          const modelAga = modelNode.replace(
            /(\[)|(\])/gm,
            function (match, p1, p2) {
              if (p1) {
                return ".";
              } else if (p2) {
                return "";
              } else {
                return match;
              }
            }
          );
          //进行深度写入
          const modelLeve = modelAga.split(".");
          let copyData = this;
          for (let j = 0, l = modelLeve.length; j < l; j++) {
            const iterKey = modelLeve[j];
            if (iterKey) {
              const iterObj = copyData[iterKey];
              if (j == l - 1) {
                // 如果value为一个函数  则把 之前的value 作为函数的第一个值
                // 函数 this  指向调用
                const finalModelVal = copyData[iterKey];

                // 是否对vue数组对象类型进行深度响应  Array.prototype.splice
                const deepUse =
                  modelConfig.deep && comUtils.toClass(copyData) === "Array";
                if (deepUse) {
                  if (!valueIsFun) {
                    copyData.splice(iterKey, 1, value);
                  } else {
                    copyData.splice(
                      iterKey,
                      1,
                      value.call(this, finalModelVal)
                    );
                  }
                } else {
                  if (!valueIsFun) {
                    copyData[iterKey] = value;
                  } else {
                    copyData[iterKey] = value.call(this, finalModelVal);
                  }
                }
                // 载入路径收集
                modelAss_path[modelNode] = copyData[iterKey];
                break;
              } else {
                copyData = iterObj;
              }
            } else {
              throw new Error("iter leve is not be Null");
            }
          }
          if (modelHasCallback) {
            let copyKey = modelLeve[0];
            // 完整对象写入
            modelAss[copyKey] = this[copyKey];
          }
        });
      });
    } else {
      console.warn("AsskeyObj keys length is 0");
    }
    if (modelHasCallback) {
      modelConfig.callback.call(this, modelAss, modelAss_path);
    }
    // 如果写入模式返回 写入路径与值 对象
    if( modelType ){
      return modelAss_path;
    }else{
      const modelAssVals = Object.values( modelAss_path )
      // 如果是 获取模式
      // 字符串模式  直接返回获取值
      if( keyObjType === "String" ){
        return modelAssVals[0]
      }else{
        // 数组模式   按顺序返回获取值数组集合
        return modelAssVals
      }
    }
  } else {
    throw new Error("missing necessary params");
  }
};


//  基于model函数   
//  针对vuex state  载入函数
 function commit_model(state, commitConfig) {
  if (commitConfig && comUtils.isType(commitConfig, "Object")) {
    const { modelInfo, modelConfig } = commitConfig;
    if (modelInfo && comUtils.isType(modelInfo, "Object")) {
      const setConfigDefault = {
        deep: true,
      };
      if (modelConfig) {
        const modelConfigType = comUtils.toClass(modelConfig);
        if (modelConfigType === "String") {
          setConfigDefault.module = modelConfig;
        } else if (modelConfigType === "Function") {
          setConfigDefault.callback = modelConfig;
        } else if (modelConfigType === "Object") {
          Object.assign(setConfigDefault, modelConfig);
        } else {
          throw Error(
            "param commitConfig.modulePath  type must be String or Object"
          );
        }
      }
      model.call(state, modelInfo, null, setConfigDefault);
    } else {
      throw Error(
        "param commitConfig.modelInfo is required or type must be Object"
      );
    }
  } else {
    throw Error("param commitConfig is required or type must be Object");
  }
};


module.exports = {
  model
  ,commit_model
}